﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Reflection;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Core;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;

namespace AppFuscator
{
    internal class ApiRequest : WebRequest
    {
        private static volatile ApiRequest instance;
        private static object syncRoot = new object();

        public static ApiRequest Instance
        {
            get
            {
                if (instance == null)
                {
                    lock (syncRoot)
                    {
                        if (instance == null)
                            instance = new ApiRequest();
                    }
                }

                return instance;
            }
        }

        public string Host { get; set; }
        public bool IsUploading { get; set; }

        private ApiRequest()
        {
            Host = Utils.MainHost;
        }

        public bool? Initialize()
        {
            // Set cookies
            string link = "http://" + Host + "/api/init";
            string response = CreateGet(link);

            if (response == null)
                return null;

            Version need;
            try
            {
                need = Version.Parse(Utils.GetTextFromApi(response));
                User.Instance.LoadFromAnswer(response);
            }
            catch
            {
                return null;
            }

            return Assembly.GetExecutingAssembly().GetName().Version >= need;
        }

        public bool Begin()
        {
            // Set cookies
            string link = "http://" + Host + "/api/clear";
            string response = CreateGet(link);
            if (response == null)
                return false;

            return (Utils.GetTextFromApi(response) == "ok");

        }

        public int UploadFile(string pathToFile, bool main)
        {
            string link = "http://" + Host + "/api/upload";

            #region Заполнение формы (имя файла и его параметры)

            string boundary = "----" + DateTime.Now.Ticks;
            IDictionary<string, string> dict = new Dictionary<string, string>();
            dict.Add("main_asm", main.ToString());
            dict.Add("file", Path.GetFileName(pathToFile));
            //dict.Add("AjaxUpload", "true");
            string formData = GetFormData(dict, boundary);
            formData += "--" + boundary + "\nContent-Disposition: form-data; name=\"file\"; filename=\"" +
                        Path.GetFileName(pathToFile) + "\""
                        + "\nContent-Type: application/octet-stream\n\n";

            #endregion Заполнение формы (имя файла и его параметры)


            return CreatePostWithFile(link, formData, pathToFile, boundary);
        }

        public string GenerateKey()
        {
            string link = "http://" + Host + "/api/key";
            string data = CreateGet(link);

            if (data != null)
            {
                JObject jsonData = Utils.ParseJson(data);

                if (jsonData.Property("status").Value.ToString().ToLower() == "success")
                    data = Utils.GetTextFromApi(data);
                else
                    data = null;
            }

            return data;
        }

        public int SetField(IDictionary<string, string> dictionary)
        {
            string link = "http://" + Host + "/api/start";

            #region Заполнение формы (отмеченные свойства каждого файла)

            string boundary = "----" + DateTime.Now.Ticks;
            var dict = new Dictionary<string, string>();
            foreach (var d in dictionary)
            {
                dict.Add(d.Key, d.Value);
            }
            string formData = GetFormData(dict, boundary) + "--" + boundary + "--";

            #endregion Заполнение формы (отмеченные свойства каждого файла)

            try
            {
                string info = CreatePost(link, formData, boundary);
                if (info == null)
                    throw new Exception();
            }
            catch
            {
                throw new UploadException("Can not send info about all assseblies to the server.");
            }

            try
            {
                string link2 = "http://" + Host + "/api/run";

                string response = CreateGet(link2);
                if (response == null)
                    throw new Exception();

                response = Utils.GetTextFromApi(response);

                return int.Parse(response);
            }
            catch
            {
                
                throw new UploadException("Start obfuscation failed.");
            }
        }

        public AppfuscatorResult GetResult(int obfuscationId)
        {
            IsUploading = true;
            string link = "http://" + Host + "/api/result?obfuscation=" + obfuscationId.ToString();
            var result = CreateGet(link);
            IsUploading = false;
            return AppfuscatorResult.Create(result);
        }

        public AuthStatus Auth(string email, string password)
        {
            string link = "http://" + Host + "/api/auth?email=" + email + "&password=" + password;
            string answer = CreateGet(link);
            string result = Utils.GetTextFromApi(CreateGet(link));
            switch (result)
            {
                case "ok":
                    User.Instance.LoadFromAnswer(answer);
                    return AuthStatus.Ok;
                case "notactive":
                    return AuthStatus.NotActive;
                case "notfound":
                    return AuthStatus.NotFound;
                default:
                    return AuthStatus.Unknown;
            }
        }

        public void ExtractZip(AppfuscatorResult res, string outFolder)
        {
            WebClient wc = new WebClient();
            string tmp = Path.GetTempFileName();
            string path = "http://" + Utils.MainHost + "/api/download/?obfuscation=" + res.obfuscationId.ToString() + "&ssid=" + Utils.EncodeByMyAlgo(GetSsid());
            wc.DownloadFile(path, tmp);

            using (FileStream fs = File.OpenRead(tmp))
            {
                var zip = new ZipFile(fs);
                foreach (ZipEntry zipEntry in zip)
                {
                    if (!zipEntry.IsFile) continue;

                    byte[] buffer = new byte[4096];
                    Stream zipStream = zip.GetInputStream(zipEntry);

                    // Manipulate the output filename here as desired.
                    String fullZipToPath = Path.Combine(outFolder, zipEntry.Name);
                    string directoryName = Path.GetDirectoryName(fullZipToPath);
                    if (directoryName.Length > 0 && !Directory.Exists(directoryName))
                        Directory.CreateDirectory(directoryName);
                    
                    using (FileStream streamWriter = File.Create(fullZipToPath))
                    {
                        StreamUtils.Copy(zipStream, streamWriter, buffer);
                    }
                }
            }
        }

        private string GetFormData(IEnumerable<KeyValuePair<string, string>> dict, string boundary)
        {
            string formData = "";
            foreach (var dc in dict)
            {
                formData += InitLineFormData(boundary, dc.Key, dc.Value) + "\n";
            }
            return formData;
        }

        private string InitLineFormData(string boundary, string key, string value)
        {
            return "--" + boundary + "\nContent-Disposition: form-data; name=\"" + key + "\"\n\n" + value;
        }

        public void Logout()
        {
            string link = "http://" + Host + "/api/logout";
            CreateGet(link);
        }

        private string GetSsid()
        {
            CookieCollection cookies = cookieCont.GetCookies(new Uri("http://" + Utils.MainHost));
            for (int i = 0; i < cookies.Count; i++)
            {
                if (cookies[i].Name == "___user_prefs_data")
                    return cookies[i].Value;
            }
            throw new Exception("No cookies found");
        }
    }
}